//------------------------------------------------------------------------------------------------//
//                                                                                                //
//                                       M u s i c F o n t                                        //
//                                                                                                //
//------------------------------------------------------------------------------------------------//
// <editor-fold defaultstate="collapsed" desc="hdr">
//
//  Copyright © Hervé Bitteur and others 2000-2017. All rights reserved.
//
//  This program is free software: you can redistribute it and/or modify it under the terms of the
//  GNU Affero General Public License as published by the Free Software Foundation, either version
//  3 of the License, or (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
//  without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
//  See the GNU Affero General Public License for more details.
//
//  You should have received a copy of the GNU Affero General Public License along with this
//  program.  If not, see <http://www.gnu.org/licenses/>.
//------------------------------------------------------------------------------------------------//
// </editor-fold>
package org.audiveris.omr.ui.symbol;

import org.audiveris.omr.OMR;
import org.audiveris.omr.glyph.Shape;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.awt.Dimension;
import java.awt.Font;
import java.awt.font.TextLayout;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.util.HashMap;
import java.util.Map;

/**
 * Class {@code MusicFont} is meant to simplify the use of the underlying music font
 * when rendering score views.
 * <p>
 * The strategy is to use a properly scaled instance of this class to carry out the drawing of music
 * symbols with the correct size. The scaling should combine two factors:<ul>
 * <li>An interline value different from default {@link #DEFAULT_INTERLINE}</li>
 * <li>A zoom ratio different from 1</li>
 * </ul>
 * To get a properly scaled instance, use the convenient method {@link #getFont(int)} which expects
 * a staff height expressed in a count of screen pixels (which thus combines the 2 factors listed
 * above).</p>
 * <p>
 * There are two well-known pre-scaled instances of this class:<ul>
 * <li>{@link #baseMusicFont} for standard symbols (scale = 1)</li>
 * <li>{@link #iconMusicFont} for icon symbols (scale = 1/2)</li>
 * </ul>
 * The underlying font is <b>MusicalSymbols</b>.
 *
 * @see
 * <a href="http://fonts.simplythebest.net/font/108/musical_symbols-font.font">http://fonts.simplythebest.net/font/108/musical_symbols-font.font</a>
 *
 * @author Hervé Bitteur
 */
public class MusicFont
        extends OmrFont
{
    //~ Static fields/initializers -----------------------------------------------------------------

    private static final Logger logger = LoggerFactory.getLogger(MusicFont.class);

    /**
     * The music font name: {@value} (no other one is used).
     * Possibilities: MusicalSymbols, Symbola, Bravura
     */
    public static final String FONT_NAME = "MusicalSymbols";

    /** Interline extension for better results with MusicalSymbols font. */
    private static final int GLOBAL_INTERLINE_EXTENT = 1;

    /** Interline extension, meant for NOTEHEAD_VOID often too small. */
    public static final int NOTEHEAD_VOID_EXTENT = 1;

    /**
     * Offset for code range.
     * 0xf000 for MusicalSymbols
     * 0x1d100 for Symbola or Bravura
     */
    public static final int CODE_OFFSET = 0xf000;

    /** Cache of font according to desired interline value */
    private static final Map<Integer, MusicFont> sizeMap = new HashMap<Integer, MusicFont>();

    /** The music font used for default interline and no zoom */
    public static final MusicFont baseMusicFont = getFont(DEFAULT_INTERLINE);

    /** The music font used for icons (half-size) */
    public static final MusicFont iconMusicFont = getFont(DEFAULT_INTERLINE / 2);

    //~ Instance fields ----------------------------------------------------------------------------
    /** Precise interline height with this font. */
    private final int fontInterline;

    //~ Constructors -------------------------------------------------------------------------------
    /**
     * Creates a new MusicFont object.
     *
     * @param sizePts       the point size of the {@code Font}
     * @param fontInterline the (zoomed?) interline value for this font
     */
    MusicFont (int sizePts,
               int fontInterline)
    {
        super(FONT_NAME, Font.PLAIN, sizePts);
        this.fontInterline = fontInterline;
    }

    //~ Methods ------------------------------------------------------------------------------------
    //------------//
    // buildImage //
    //------------//
    /**
     * Build an image from the shape definition in MusicFont, using the
     * scaling determined by the provided interline value.
     *
     * @param shape     the desired shape
     * @param interline the related interline value
     * @param decorated true if shape display must use decorations
     * @return the image built with proper scaling, or null
     */
    public static BufferedImage buildImage (Shape shape,
                                            int interline,
                                            boolean decorated)
    {
        MusicFont font = getFont(interline);

        return font.buildImage(shape, decorated);
    }

    //------------//
    // buildImage //
    //------------//
    /**
     * Build an image from the shape definition in MusicFont, using the
     * intrinsic scaling of this font.
     *
     * @param shape     the desired shape
     * @param decorated true if shape display must use decorations
     * @return the image built with proper scaling, or null
     */
    public BufferedImage buildImage (Shape shape,
                                     boolean decorated)
    {
        ShapeSymbol symbol = Symbols.getSymbol(shape, decorated);

        if (symbol == null) {
            return null;
        } else {
            return symbol.buildImage(this);
        }
    }

    //----------------//
    // checkMusicFont //
    //----------------//
    /**
     * Check whether we have been able to load the font.
     *
     * @return true if OK
     */
    public static boolean checkMusicFont ()
    {
        if (baseMusicFont.getFamily().equals("Dialog")) {
            String msg = FONT_NAME + " font not found." + " Please install " + FONT_NAME + ".ttf";
            logger.error(msg);

            if (OMR.gui != null) {
                OMR.gui.displayError(msg);
            }

            return false;
        }

        return true;
    }

    //---------//
    // getFont //
    //---------//
    /**
     * Report the (cached) best font according to the desired interline value.
     *
     * @param interline the desired (zoomed) interline in pixels
     * @return the font with proper size
     */
    public static MusicFont getFont (int interline)
    {
        interline += GLOBAL_INTERLINE_EXTENT;

        MusicFont font = sizeMap.get(interline);

        if (font == null) {
            font = new MusicFont(4 * interline, interline);
            sizeMap.put(interline, font);
        }

        return font;
    }

    //--------//
    // layout //
    //--------//
    /**
     * Build a TextLayout from a Shape, using its related String of MusicFont characters,
     * and potentially sized by an AffineTransform instance.
     *
     * @param shape the shape to be drawn with MusicFont chars
     * @param fat   potential affine transformation
     * @return the (sized) TextLayout ready to be drawn
     */
    public TextLayout layout (Shape shape,
                              AffineTransform fat)
    {
        ShapeSymbol symbol = Symbols.getSymbol(shape);

        if (symbol == null) {
            logger.debug("No MusicFont symbol for {}", shape);

            return null;
        }

        return layout(symbol.getString(), fat);
    }

    //--------//
    // layout //
    //--------//
    /**
     * Build a TextLayout from a Shape, using its related String of
     * MusicFont characters, and sized to fit the provided dimension.
     *
     * @param shape     the shape to be drawn with MusicFont chars
     * @param dimension the dim to fit as much as possible
     * @return the adjusted TextLayout ready to be drawn
     */
    public TextLayout layout (Shape shape,
                              Dimension dimension)
    {
        ShapeSymbol symbol = Symbols.getSymbol(shape);

        if (symbol != null) {
            return layout(symbol, dimension);
        } else {
            return null;
        }
    }

    //--------//
    // layout //
    //--------//
    /**
     * Build a TextLayout from a symbol, using its related String of
     * MusicFont characters, and sized to fit the provided dimension.
     *
     * @param symbol    the symbol to be drawn with MusicFont chars
     * @param dimension the dim to fit as much as possible
     * @return the adjusted TextLayout ready to be drawn
     */
    public TextLayout layout (BasicSymbol symbol,
                              Dimension dimension)
    {
        String str = symbol.getString();
        TextLayout layout = new TextLayout(str, this, frc);

        // Compute proper affine transformation
        Rectangle2D rect = layout.getBounds();
        AffineTransform fat = AffineTransform.getScaleInstance(
                dimension.width / rect.getWidth(),
                dimension.height / rect.getHeight());

        return layout(str, fat);
    }

    //--------//
    // layout //
    //--------//
    /**
     * Build a TextLayout from a Shape, using its related String of
     * MusicFont character codes.
     *
     * @param shape the shape to be drawn with MusicFont chars
     * @return the TextLayout ready to be drawn
     */
    public TextLayout layout (Shape shape)
    {
        return layout(shape, (AffineTransform) null);
    }

    //--------//
    // layout //
    //--------//
    /**
     * Build a TextLayout from a String of MusicFont character codes.
     *
     * @param codes the MusicFont codes
     * @return the TextLayout ready to be drawn
     */
    public TextLayout layout (int... codes)
    {
        return layout(new String(codes, 0, codes.length));
    }

    //------------------//
    // getFontInterline //
    //------------------//
    /**
     * Report the number of pixels of the interline that corresponds to
     * this font.
     *
     * @return the scaled interline for this font
     */
    protected int getFontInterline ()
    {
        return fontInterline;
    }
}
